home *** CD-ROM | disk | FTP | other *** search
/ Amiga Mag HDD Backup / Amiga Mag HDD Backup.zip / Amiga Mag HDD Backup / Alexander.img.bin / Alexander.img / ***9.11 All NEWer important / Stockman⁄StructDraw Post / PSlang.asc < prev    next >
Text File  |  1994-09-16  |  37KB  |  686 lines

  1. Creating Structured Drawings - Part One: PostScript
  2.  
  3. The Amiga has always been known as a powerful graphics computer.  Creating complex paintings and 
  4. drawings is almost trivial.  The early introduction of the ILBM IFF standard for storing bitmap graphics 
  5. by Electronic Arts eased the process of sharing bitmap graphic images.  Unfortunately, sharing 
  6. structured drawing images between programs has not been as easy.  There is no one standard format that 
  7. is widely used by all Amiga programs.  This causes headaches for users, and requires the programmer to 
  8. support a number of structured drawing formats.  Finding information about the commonly used formats 
  9. and learning their syntax requires considerable effort.  This article is the first of a series that will discuss 
  10. two commonly used structured drawing formats.  It is hoped that by providing references and simple 
  11. examples, these formats will be included in more programs.
  12.  
  13. Structured drawings and bitmap drawings may appear the same on screen, but they are stored quite 
  14. differently.  A structured drawing is best used for line art and complex figures, while bitmap images are 
  15. better for scanned photographs and artwork composed of many colors.  Each element (e.g. line, 
  16. rectangle, circle, etc.) of a structured drawing is saved as an equation that very precisely defines that 
  17. element.  Therefore, when that drawing is printed to a device that has a higher resolution than the screen, 
  18. each element is recreated from the original equation to take advantage of the highest resolution possible.  
  19. This avoids the dreaded "jaggies".  A drawing saved as a bitmap does not save specific information 
  20. about each element of a drawing.  A bitmap contains information about the color of each pixel on screen.  
  21. Therefore, if your screen image is 640x400 pixels (75 dpi commonly), printing that image to a 300 dpi 
  22. printer (2560x3300 printer dots) either results in a very small image (~2x1.34 inches using 300 dpi dots), 
  23. or a larger image composed of big jaggy dots.  Because bitmaps contain the color information for every 
  24. pixel, they are best suited for images that have many colors of similar hues.
  25.  
  26. In this and the following series of articles, I will discuss the basics of two structured drawing formats.  
  27. These are PostScript (Adobe Systems), and HP-GL/2 (Hewlett-Packard).  The IFF DR2D format is not 
  28. discussed in this series because that task can be better handled by its creator (Stylus).  I have included a 
  29. C program called StructDrawing.c that demonstrates some of the concepts in these articles.  Although I 
  30. do not discuss the DXF (AutoDesk) format in the text, StructDrawing.c includes rudimentary code for 
  31. creating DXF files.  This is a structured drawing format commonly using by MS-DOS-based machines 
  32. and by CAD programs.  Programmers interested in supporting the DXF format should contact AutoDesk 
  33. for a copy of the DXF guidelines (415/491-8736).  Compared to the documentation available for 
  34. PostScript and HP-GL/2, I found the DXF documentation very limited.
  35.  
  36. Given the complexity of these formats, I cannot cover the entire scope of each standard in one or two 
  37. articles.  My aim is to introduce the basics of each format to 1) point the user in the correct direction for 
  38. further study, and 2) provide simple C code examples of how to create structured drawings using each 
  39. format.  References for further study of each format are provided.
  40.  
  41.  
  42. PostScript
  43. PostScript is probably the best known and most commonly used format for saving structured drawings.  
  44. Calling PostScript a structured drawing format is not quite accurate and almost belittling.  PostScript is 
  45. actually a rich and complex programming language that is first and foremost a page description 
  46. language.  Its handling of text has made it the most commonly used format for desktop publishing.  
  47. Structured graphics are often saved in Encapsulated PostScript (EPS) format.  EPS format is a PostScript 
  48. file with a few extra commands.  Because this article is about structured drawings, it will focus on the 
  49. graphics object commands and not so much on the many programming and text commands.
  50.  
  51. From a beginning programmer's perspective, probably the best property of PostScript files is that they 
  52. are simple ASCII text.  This means a PostScript structured drawing can be created using a text editor.  
  53. For example, to create a line from the lower left corner of a page to the upper right corner of a page, the 
  54. following text can be entered into a text editor, saved, and then sent to a PostScript capable device.
  55.  
  56. newpath
  57. 72 72 moveto
  58. 576 720 lineto
  59. stroke
  60. showpage
  61.  
  62. newpath clears any previously defined graphics path.  72 72 moveto moves an imaginary graphics pen 
  63. that is raised off the paper to X position 72 and Y position 72 (the significance of 72 will be discussed 
  64. shortly).  576 720 lineto puts the pen down on the paper at the pen's current location (set by the moveto 
  65. command) and draws an imaginary line to point 576, 720 on the page.  The drawn line is called 
  66. imaginary because the line is not drawn on the page until the stroke command is received.  showpage 
  67. completes all drawing to the current page and either displays the page or outputs the paper.  If the user 
  68. deletes the stroke command, the line will not be drawn on the paper, resulting in a blank page.  The 
  69. lineto and moveto commands are considered path commands.  They define a specific path, but they do 
  70. not apply "ink" to the page.  Applying ink to the page occurs by use of the stroke or fill operators.
  71.  
  72. As with most things in life, learning PostScript is best achieved by practice.  Amiga users are fortunate 
  73. to have two freely distributable programs, Post and GhostScript, that can interpret PostScript code and 
  74. either draw it to the screen or to a Preferences supported printer.  The method I found useful was to have 
  75. the PostScript interpreter running in the background while I created simple PostScript code in an editor 
  76. (Ed or memacs will do).  The PostScript code is saved to a file, loaded into the interpreter, and then 
  77. drawn to screen by the interpreter.  This gives immediate feedback while not wasting paper.  I also did 
  78. not have a PostScript-capable printer when I learned PostScript.
  79.  
  80. The PostScript language uses a stack and relies on postfix notation.  A stack is an area of memory that 
  81. acts kind of like a stack of books.  As one book is placed on top of another, the most accessible book 
  82. will always be the book on top of the stack.  In other words, the last book on the stack is the first book 
  83. that will come off the stack.  A simple example in PostScript would be the addition of two numbers, 72 
  84. 144 add.  72 is placed on the stack first, then 144, and finally the results of the addition 216 would be at 
  85. the top of the stack and most accessible for other operations.
  86.  
  87. Postfix notation refers to the fact that the parameters precede the command as in 72 144 moveto.  
  88. Performing a similar function using an Amiga graphics command appears as Move(rp, 72, 144).  The C 
  89. programming language and the Amiga graphics commands do not use postfix notation.  Forth 
  90. programmers will feel right at home with PostScript, while the rest of us have to keep reminding 
  91. ourselves what this "backwards" way of doing things really means.  For the simple graphics commands 
  92. we will look at, understanding the stack and postfix notation concepts are not essential.  The 
  93. programmer must just remember that the parameters precede the command.  For PostScript 
  94. programmers who write PostScript functions, understanding the stack and postfix notation is essential.
  95.  
  96. The PostScript language is page oriented.  It works on only one page at a time.  Each page has on it, a 
  97. user coordinate system.  The default user coordinate space has 72 units per inch.  Position 0, 0, or the 
  98. page's origin, is located at the lower left corner of the page.  Moving to the right increases the X, or 
  99. horizontal, value by 72 units for every inch moved.  Moving up on the page increases the Y value by 72 
  100. units for each inch.  Therefore, the upper right corner of an 8.5x11 inch page is location 612, 792.  The 
  101. resolution of a PostScript image is not limited to 1/72's of an inch.  Actually, the resolution is controlled 
  102. by the output device.  An RIP equipped Linotronic can have a resolution finer than 1/2000th of an inch.  
  103. Therefore a command such as 72.125 144.500 moveto is perfectly acceptable.
  104.  
  105. Most humans do not think in terms of 1/72nd's of an inch.  It is much more convenient to work in 
  106. decimal inches.  For example, a distance of 1.5 inches is more easily understood than 108 units when 
  107. trying to place an object on a page.  By creating a PostScript procedure, we can use inches in our 
  108. PostScript file and allow PostScript to convert our inch measurements into 72nd's of an inch.  Let us 
  109. re-write our previous example to use inches instead of the default PostScript units.
  110.  
  111. /inch {72 mul} def
  112. newpath
  113. 1.0 inch 1.0 inch moveto
  114. 8.0 inch 10.0 inch lineto
  115. stroke
  116. showpage
  117.  
  118. Our PostScript procedure is the first line "/inch {72 mul} def".  Instead of saying 72 72 moveto as in 
  119. our first example, we can now write 1.0 inch 1.0 inch moveto.  The PostScript interpreter places 1.0 on 
  120. the stack.  When it encounters the word inch, it looks in its dictionary for that word.  It finds that we 
  121. have defined the word inch to mean multiply the top value on the stack by 72 and place the results on the 
  122. stack (1.0 72 mul).  If you prefer to work in centimeters, you can write a similar definition that performs 
  123. the same function.
  124.  
  125. /cm {28.346 mul} def
  126.  
  127. It is important to note that your definition only takes effect for all lines after the location of your 
  128. definition.  In addition, do not equate a definition with a PostScript command word.  For example, you 
  129. do not want to alter the meaning of moveto by re-defining it (e.g. /moveto {-1.0 mul} def).  For the rest 
  130. of this article, we will work in inches and not the default PostScript defined values when referring to 
  131. page coordinates.  We will still use the default 72nd's of an inch when discussing line thickness and a 
  132. text's point size (1 point equals approximately 1/72nd's of an inch).
  133.  
  134. The first example created a 1 point wide black line.  PostScript makes it easy to alter the thickness and 
  135. darkness of a line.  The thickness of a line is set by the command setlinewidth.  The default line width is 
  136. one default page unit.  To create a thick line, we would use setlinewidth with some number greater than 
  137. 1 (e.g. 5 setlinewidth).  To create a thinner line, we can write something like 0.25 setlinewidth.  This 
  138. line width will stay in effect until another setlinewidth command is issued.
  139.  
  140. To change the level of gray of the line, we use the setgray command.  The setgray command takes one 
  141. argument, the level of gray.  0 signifies black while 1 means white.  The command 0.5 setgray produces 
  142. a medium gray line.  This command stays in effect until another setgray command is received (or 
  143. another set color type command, e.g. setrgb).
  144.  
  145. We now know enough about PostScript programming to create a couple of functions that perform 
  146. similar operations to the Amiga's Move() and Draw() graphics commands.  For many programming 
  147. projects, it may be most efficient to write each graphics function so that it can send graphics commands 
  148. to either an Amiga window or to a PostScript capable device.  This method allows us to either send a 
  149. structured drawing to screen or to a PostScript capable device by changing only one variable.  As a 
  150. simple example, here is a short C function that can either move the screen cursor or the PostScript 
  151. cursor, depending on where we want the output to go.
  152.  
  153. /*  Global definitions */
  154. #define    SCREEN        0
  155. #define    PS                1
  156. #define    MAP_X(x)    (10+(x))        /* offset some for left border */
  157. #define    MAP_Y(y)    (384-(y))        /* 384 being height of drawing area */
  158. #define    MAP_PSX(x)        (0.25+(PS_Width/W_Width * (x)))
  159. #define    MAP_PSY(y)        (0.25+(PS_Height/W_Height*(W_Height-(y)))
  160.  
  161. /*  Global variables */ 
  162. UBYTE Output_Device= SCREEN;
  163. struct RastPort *sd_rp=NULL; 
  164. FILE *Outfp=NULL;
  165.  
  166. /*    call sd_Move() by first converting values to screen values, such as:
  167.     sd_Move(MAP_X(x), MAP_Y(y));
  168. */
  169.  
  170. void sd_Move(float x, float y){
  171.      if(Output_Device==SCREEN){
  172.          Move(sd_rp, (int)(x+0.5), (int)(y+0.5));
  173.      }
  174.      else if(Output_Device==PS){
  175.          fprintf(Outfp, "%f inch %f inch moveto\n", MAP_PSX(x),MAP_PSY(y));
  176.      }
  177.      return;
  178.  }
  179.  
  180. The #define MAP_ ... statements deal with scaling the drawing correctly for the selected output type.  
  181. The global variable Output_Device allows us to select where we want output sent.  As we write more 
  182. functions, we will see that by changing one variable, Output_Device, we can redirect the drawing of a 
  183. complex graphic image to any number of output devices.  This method may not work in all applications, 
  184. and the most efficient PostScript code is not created, but it has significant merit in terms of ease of 
  185. implementation and the range of formats that can be easily supported.
  186.  
  187. The next function draws a line from the current imaginary pen location to the user-defined new location.
  188.  
  189. void sd_Draw(float x, float y){
  190.  
  191.     if(Output_Device==SCREEN){
  192.          Draw(sd_rp, (int)(x+0.5), (int)(y+0.5));
  193.      }
  194.      else if(Output_Device==PS){
  195.          fprintf(Outfp, "%f inch %f inch lineto\n", MAP_PSX(x), MAP_PSY(y));
  196.      }
  197.      return;
  198.  }
  199.  
  200. Because PostScript can control the thickness of a line while the Amiga graphics commands cannot, we 
  201. may want to write a function that will do this whether the line is drawn to the screen or to a PostScript 
  202. device.
  203.  
  204. void sd_DrawLine(int l_thick, float x1,float y1, float x2, float y2){
  205.  
  206.      int i=0;
  207.  
  208.      if(Output_Device==SCREEN){
  209.          for(i=0;i<l_thick;i++){
  210.             if(x1!=x2){        /* note, there are better algorithms than this to deal with */
  211.                             /* correctly drawing variable line thickness */
  212.                 Move(sd_rp, (int)(x1+0.5), (int)(y1+0.5+l_thick/2.-i));
  213.                 Draw(sd_rp, (int)(x2+0.5), (int)(y2++0.5l_thick/2.-i));
  214.             }
  215.             else{
  216.                 Move(sd_rp, (int)(x1+0.5+l_thick/2.-i), (int)(y1+0.5));
  217.                 Draw(sd_rp, (int)(x2+0.5+l_thick/2.-i), (int)(y2+0.5));
  218.             }
  219.         }     
  220.     }
  221.      else if(Output_Device==PS){
  222.          fprintf(Outfp,"%f setlinewidth\n",(float)(l_thick));
  223.          fprintf(Outfp,"%f inch %f inch moveto\n%f inch %f inchlineto\n", x1, y1, x2, y2);
  224.      }
  225.      return;
  226.  }
  227.  
  228. The next logical element to create using PostScript is a box or rectangle.  The most basic method to 
  229. create a box is to use the moveto and lineto commands in combination to form the four sides.  Here is a 
  230. simple example.
  231.  
  232. /inch {72 mul} def
  233. newpath
  234. 5 inch 5 inch moveto
  235. 5 inch 6 inch lineto
  236. 6 inch 6 inch lineto
  237. 6 inch 5 inch lineto
  238. 5 inch 5 inch lineto
  239. stroke
  240. showpage
  241.  
  242. Because lines have thickness, the last side drawn that closed the box may have a little corner missing.  
  243. PostScript provides a command to deal with this blemish.  Instead of using the last lineto command that 
  244. closed the box, we use the command closepath.  This command will draw the last line segment and fill 
  245. in the blemish.  The box we created has four black lines that form the sides and the center is white (i.e. 
  246. no ink).
  247.  
  248. To create a filled box, we use the same PostScript code as above, but we now substitute the stroke 
  249. command with the fill command.  The result is a solid black box one inch square.  As stated previously, 
  250. the lineto and moveto commands define a path, they do not draw lines.  That is why when we just 
  251. substitute stroke (which strokes lines) with fill (which fills the defined path with the current pen) we get 
  252. an entirely different result.
  253.  
  254. By using the setgray command, it is possible to control the grayscale level of the fill.  If we place the 
  255. command 0.75 setgray anywhere before the fill command we get a 1 inch square box that is a light 
  256. shade of gray.  Looking closely at the box we just created, we realize there is no black outline along the 
  257. borders of the box.  To create a bordered box that is filled with a light gray, we must first create the path 
  258. and fill it.  Next, we recreate the same path, setgray to 0.0, and stroke it.  If we reversed the order such 
  259. that we stroked first and then filled, the black outline is obliterated by the filled box.  Overlapping 
  260. graphic images always hide whatever is underneath them.  Therefore, if we create a 1 inch square 
  261. black-filled box, but then draw a white box at the same location and same size, our paper would remain 
  262. completely unmarked.
  263.  
  264. Our PostScript box function has hardcoded dimensions.  Because a box is a commonly used geometric 
  265. shape, it is useful to write a function to create rectangles of any size, location, line thickness, and fill 
  266. type.  We can create such a function by using the simple commands we have learned so far.
  267.  
  268. void sd_Box(float x1, float y1, float x2, float y2, int l_color, int f_color, int l_thick){
  269.     int i=0;
  270.     /*    Make sure x1, y1 is upper left corner of box, required
  271.         by Amiga RectFill() command.
  272.      */
  273.      if(x1>x2){    tmp=x1;    x1=x2;    x2=tmp;}
  274.      if(y1>y2){    tmp=y1;    y1=y2;    y2=tmp;}
  275.  
  276.      if(Output_Device==SCREEN){
  277.          SetDrMd(sd_rp, JAM2);
  278.          SetAPen(sd_rp, f_color);
  279.          SetAfPt(sd_rp, pattern[f_pt], 2);
  280.          BNDRYOFF(sd_rp);
  281.         RectFill(sd_rp,(int)(x1+0.5),(int)(ymin+0.5),(int)(x2+0.5),(int)(y2+0.5));
  282.          for(i=0;i<l_thick;i++){
  283.              sd_Move((x1+i),(y1+1));
  284.              sd_Draw(l_thick,(x2-i),(y1+i));
  285.              sd_Draw(l_thick,(x2-i),(y2-i));
  286.              sd_Draw(l_thick,(x1+i),(y2-i));
  287.              sd_Draw(l_thick,(x1_i),(y1+i));
  288.          }
  289.      }
  290.      else if(Output_Device==PS){
  291.         /* first fill box */
  292.          gray_level=(GetRGB4(ssgcm,f_color))/4095.;    /* AmigaDos call */
  293.          fprintf(Outfp,"newpath\n%.3f setgray\n", graylevel);
  294.          fprintf(Outfp,"%f inch %f inch moveto\n%f inch %f inch
  295.              lineto\n",MAP_PSX(x1),MAP_PSY(y1),MAP_PSX(x2),
  296.              MAP_PSY(y1));
  297.         fprintf(Outfp,"%f inch %f inch lineto\n%f inch %f inch lineto\n",
  298.             MAP_PSX(x2),MAP_PSY(y2),MAP_PSX(x1),MAP_PSY(y2));
  299.         fprintf(Outpf,"closepath\nfill\n");
  300.         /* then outline box */
  301.         gray_level=(GetRGB4(ssgcm,l_color))/4095.;    /* AmigaDos call */
  302.          fprintf(Outfp,"newpath\n%.3f setgray\n%f setlinewidth\n", graylevel, (float)l_thick);
  303.          fprintf(Outfp,"%f inch %f inch moveto\n%f inch %f inch
  304.              lineto\n",MAP_PSX(x1),MAP_PSY(y1),MAP_PSX(x2),
  305.              MAP_PSY(y1));
  306.         fprintf(Outfp,"%f inch %f inch lineto\n%f inch %f inch lineto\n",
  307.             MAP_PSX(x2),MAP_PSY(y2),MAP_PSX(x1),MAP_PSY(y2));
  308.         fprintf(Outpf,"closepath\nstroke\n");
  309.     }
  310. return;
  311.  
  312. PostScript offers significant control over text manipulation.  For the purpose of this article we will only 
  313. discuss two basic aspects of creating textual elements.  The first aspect is how to select a font family and 
  314. scale it to a desired size.  The second aspect involves the actual printing of text on a PostScript-generated 
  315. image.
  316.  
  317. Most PostScript devices have one or more families of font typefaces hard-coded into the printer's ROM.  
  318. The more common typefaces resident in modern PostScript printers include Times-Roman, Helvetica, 
  319. and Courier.  The first step in selecting a typeface for use is to locate the typeface in the PostScript font 
  320. dictionary.  If the exact font name provided is present on the printer, the name is placed on the stack.  
  321. For example, to locate the Times-Roman typeface the following findfont command is entered:
  322.          /Times-Roman findfont
  323.  
  324. The size of the selected typeface is set by use of the scalefont command.  The one parameter for this 
  325. command is the desired height of the characters in points.  As always, the parameter precedes the 
  326. command.  To scale the Times-Roman font to 12 points, the following command is entered:
  327.      12 scalefont
  328.  
  329. Finally, to make this current typeface and size the current default we use the setfont command.  These 
  330. three commands can be written on separate lines or on the same line as:
  331.      /Times-Roman findfont 12 scalefont setfont
  332.  
  333. This typeface and size will remain the default until another setfont command is issued.  Please note that 
  334. the typeface name given must correspond exactly to the name of the typeface as it is defined in the 
  335. PostScript device.
  336.  
  337. A simple C function can be written to set the current font family and size.  As before, the global variable, 
  338. Output_Device, determines which commands to use.
  339.  
  340. void get_font(UBYTE *F_Name, float F_Sz){
  341.     if(Output_Device==SCREEN){
  342.          tAttr.ta_Name=F_Name;
  343.          tAttr.ta_YSize=(int)F_sz;
  344.          tAttr.ta_Flags=FPF_ROMFONT | FPF_DISKFONT;
  345.          if(!(tFont=(struct TextFont *) OpenDiskFont(&tAttr))){
  346.              printf("Cannot open font %s!\nUsing Topaz font!\n", F_Name);
  347.              tAttr.ta_Name="topaz.font";
  348.              if(!(tFont=(struct TextFont *)OpenDiskFont(&tAttr)))    return;
  349.          }
  350.          SetFont(sd_rp, tFont);
  351.      }
  352.     else if(Output_Device==PS){
  353.          fprintf(Outfp,"/%s findfont %.3f scalefont setfont\n",F_Name, F_Sz);
  354.      }
  355.      return;
  356. }
  357.  
  358. To actually draw a text string we use the show command.  show is a painting operator like the 
  359. previously discussed stroke and fill operators.  The simplest example of painting text involves preceding 
  360. the show command with the desired text string enclosed in parentheses.  For example, to print the text 
  361. "Hello, World", the following show command would be issued:
  362.      newpath
  363.      /Times-Roman findfont 12 scalefont setfont
  364.      1 inch 1 inch moveto
  365.      (Hello, World) show
  366.      showpage
  367.  
  368. The text is printed at the current cursor location.  The show command is similar to the Amiga's Text() 
  369. function.  We can write a simple function that allows the printing of text to either the screen or to a 
  370. PostScript-generated page.
  371.  
  372. void sd_Text(char *str, int color){
  373.     float gray_level=0.0;
  374.      if(output_Device==SCREEN){
  375.          SetDrMd(sd_rp, JAM2);
  376.          SetAPen(sd_rp, color);
  377.          Text(sd_rp, str, strlen(str));
  378.      }
  379.      else if(Output_Device==PS){
  380.         gray_level=(GetRGB4(ssgcm,color))/4095.;    /* AmigaDos call */
  381.          fprintf(Outfp,"%f setgray\n(%s) show\n", gray_level, str);
  382.      }
  383.      return;
  384.  }
  385.  
  386. Creating circles and arcs using PostScript is fairly simple by use of the arc command.  The format of the 
  387. arc command is as follows:
  388.  
  389.      xloc yloc radius startangle endangle arc
  390.  
  391. The xloc and yloc parameters determine the location of the center of the circle.  The radius parameter 
  392. sets the size of the circle/arc's radius using the current page units.  Both the startangle and endangle 
  393. parameters are in degrees.  The circle/arc is drawn in a counterclockwise direction starting on the 
  394. positive X axis.  The related arcn command draws the circle in a clockwise direction.
  395.  
  396.  To create a circle with a center at 4.25, 5.5 inches and a radius of 2 inches, the following arc command 
  397. is used:
  398.      4.25 inch 5.5 inch 2. inch 0 360 arc
  399.  
  400. The arc command is a path command just like the lineto command.  Therefore, to actually draw the 
  401. circle/arc, a stroke or fill command must be issued following the arc or arcn command.
  402.  
  403. If a current pen location is already defined when the arc command is issued, a line is drawn from the 
  404. current pen location to the location of the arc at the startangle.  Using this fact, it is a simple matter to 
  405. create a pie slice by using the moveto, closepath, and arc commands.  We first set the current pen 
  406. location to the center of the arc's radius by using the moveto command.  Next, we draw the arc.  Finally, 
  407. we use the closepath command to draw a line from the end of the arc back to the center of the arc.
  408.  
  409.      newpath
  410.      2 inch 2 inch moveto
  411.      2 inch 2 inch 1 inch 45 120 arc
  412.      closepath
  413.      stroke
  414.      showpage
  415.  
  416. Unfortunately, the Amiga's graphics library does not have a command to draw part of a circle, i.e. arc.  
  417. The current commands are limited to DrawEllipse() and DrawCircle().  We can write our own arc type 
  418. command for use on Amiga screens/windows.  The first step in creating an arc() function is to create a 
  419. function to draw polygons.  An arc can basically be thought of as a polygon with many very short sides.
  420.  
  421. A simple polygon function that draws only an outline can be created using a few simple commands.  For 
  422. the Amiga side, the outline of a polygon requires use of only the Move() and Draw() functions.  For the 
  423. PostScript side, we really only need the moveto and lineto commands.  Filling the polygon with various 
  424. patterns or colors may take significantly more work (see StructDraw.c).  The simplest polygon function 
  425. accepts an array of X, Y data pairs that define the polygon's vertices.  This is really all that is necessary 
  426. to create the polygon's outline.  An example function follows.
  427.  
  428.  void sd_Polygon(float *x, float *y, int ptsz, USHORT l_color){
  429.      int i=0;
  430.  
  431.      if(Output_Device==SCREEN){
  432.          SetAPen(sd_rp, l_color);        /* set line color */
  433.          Move(sd_rp, (int)(x[0]+0.5), (int)(y[0]+0.5));
  434.          for(i=1;i<ptsz;i++)        Draw(sd_rp, (int)(x[i]+0.5), (int)(y[i]+0.5));
  435.      }
  436.      else if(Output_Device==PS){
  437.          fprintf(Outfp,"newpath\n%.3f inch %.3f inch moveto\n", MAP_PSX(x[0]),
  438.              MAP_PSY(y[0]));
  439.          for(i=1;i<ptsz;i++)
  440.              fprintf(fp,"%.3f inch %.3f inch lineto\n",MAP_PSX(x[i]), MAP_PSY(y[i]));
  441.     }
  442.      return;
  443.  }
  444.  
  445. The next two functions combined with the sd_Polygon() function allow us to create arcs on Amiga 
  446. Screens/Windows.  The loc_pieline() function calls the sin() and cos() trigonometric functions.  On a 
  447. non-FPU system, these calls can significantly degrade performance.  Therefore, it may be wise to create 
  448. a lookup table containing trigonometric values.  Values of the sin() and cos() functions at specific angles 
  449. would be obtained from the table instead of re-calculating the values each time.
  450.  
  451.  BOOL sd_arc(float xloc, float yloc, float rad, float angstart,
  452.      float angend, float aspect, USHORT l_color){
  453.  int i=0, j=0;
  454.  float *xpie=NULL,*ypie=NULL, tmp=0.0, incr=1.0;
  455.  float xtmp=0.0, ytmp=0.0, endang=0.0;
  456.  
  457.  tmp=floor(angstart);
  458.  endang=floor(angend);
  459.  if(endang==359.0)    endang=360.0;
  460.  
  461.  if(Output_Device==SCREEN){
  462.      xpie=malloc(740 * sizeof(float));
  463.      ypie=malloc(740 * sizeof(float));
  464.      if(!xpie || !ypie){
  465.          if(xpie)    free(xpie);
  466.          if(ypie)    free(ypie);
  467.          return(FALSE);
  468.      }
  469.      /* aspect determines how elliptical to make the polygon, this is important when drawing
  470.         to a screen that does not have square pixels (e.g. 640x400)
  471.     */
  472.      loc_pieline(rad,tmp, aspect, &xtmp, &ytmp);
  473.      xpie[0]=MAP_X(xloc);    ypie[0]=MAP_Y(yloc);
  474.      xpie[1]=MAP_X(xloc+xtmp);    ypie[1]=MAP_Y(yloc+ytmp);
  475.      i=2;
  476.      while(tmp<=endang && i<730){
  477.          loc_pieline(rad, tmp, aspect, &xtmp, &ytmp);
  478.          xpie[i]=MAP_X(xloc+xtmp);
  479.          ypie[i++]=MAP_Y(yloc+ytmp);
  480.          tmp += incr;
  481.          xtmp=ytmp=0.0;
  482.      }
  483.      xpie[i]=MAP_X(xloc);    ypie[i]=MAP_Y(yloc);    /* close slice */
  484.      i++;
  485.      sd_polygon(xpie, ypie, i, l_color, 0);    /*0==line pattern*/
  486.      free(xpie);    free(ypie);
  487.  }
  488.  else if(Output_Device==PS){
  489.       fprintf(fp,"%.3f setgray\n",((GetRGB4(ssgcm,l_color))/4095.));
  490.     /* The init file sd.PSinit, contains a PS procedure called ellipse that takes */
  491.     /* the aspect parameter into account.  For this example we will use arc     */
  492.      fprintf(fp,"newpath\n%.3f inch %.3f inch %.3f inch %.3f inch
  493.          %.3f %.3f arc\n", MAP_PSX(MAP_X(xloc)), MAP_PSY(MAP_Y(yloc)),
  494.         rad,angstart, angend);
  495.  }
  496.  return(TRUE);
  497.  }
  498.  
  499.  #define DTOR    0.017453    /* used to convert from degrees to radians */
  500. void loc_pieline(float radius, float angle, float aspect,float *xt, float *yt){
  501.      if(aspect<1.0){
  502.          *xt=cos(angle * DTOR) * radius;
  503.          *yt=-aspect * sin(angle * DTOR) * radius;
  504.      }
  505.      else{
  506.          *xt=cos(angle * DTOR) * radius/aspect;
  507.          *yt=-sin(angle * DTOR) * radius;
  508.      }
  509.  return;
  510.  }
  511.  
  512. Although we have just scratched the surface of the graphics commands available in the PostScript 
  513. language, we have constructed all the functions necessary to create simple geometric shapes.  There are a 
  514. few more commands that many users will need.
  515.  
  516. PostScript has three operators that can alter the coordinate system.  The translate command moves the 
  517. entire coordinate system on a page by a user-specified amount.  The rotate command rotates the 
  518. coordinate system by a user-specified number of degrees.  The scale command alters the scale of the 
  519. coordinate units.
  520.  
  521. The translate command moves the origin of the user coordinate system.  For example, if a translate 
  522. command were issued that moved the origin (0, 0) up one inch and to the right one inch, a subsequent 
  523. graphics operation draws the object one inch up and to the right of where expected.
  524.  
  525. The translate command takes two parameters, an X and a Y value as demonstrated on the following line.
  526.      72 72 translate
  527. Now that we know the structure of the translate command, we can demonstrate how it works.
  528.  
  529.     newpath
  530.     1 inch 1 inch moveto
  531.     2 inch 2 inch lineto
  532.     stroke
  533.     1 inch 1 inch translate
  534.     2 inch 2 inch moveto
  535.     3 inch 2 inch lineto
  536.     stroke
  537.     showpage
  538.  
  539. The first line is drawn as expected.  It begins at one inch up and one inch to the right of the lower left 
  540. corner of the page and extends to the two inch, two inch location.  Our second line should have started at 
  541. the end of our first line (2 inch 2 inch moveto), but it actually started at the three inch, three inch mark.  
  542. Basically, our entire page coordinate system was moved.  Now when we draw a point at a location such 
  543. as 1 inch 1 inch, the point is actually drawn at a location 1 inch up and 1 inch to the right of expected (2 
  544. inch 2 inch).
  545.  
  546. Please note that the translate operation only affects all commands after the translate operation.  In 
  547. addition, subsequent translate operations are additive.  Therefore, if we enter two translate commands 
  548. such as the following:
  549.  
  550.      1 inch 1 inch translate
  551.      1 inch 1 inch translate
  552.  
  553. our coordinate system is moved up 2 inches and to the right 2 inches.
  554.  
  555. The translate command makes it easy to draw complex patterns or drawings multiple times on a page 
  556. with only a few commands.  If a complex shape were made into a definition, an entire page could be 
  557. tiled with that shape.  Assume a complex shape that has the definition name "butterfly".  We could tile 
  558. the entire page by placing two lines of PostScript code inside a loop.
  559.      xloc yloc translate
  560.      butterfly
  561.  
  562. Another use for the translate command is to place an entire graph or drawing that is smaller than the 
  563. physical page at a specific location on that larger page.  In addition, the translate command in 
  564. combination with the rotate command allows us to alter the page orientation from Portrait to Landscape.
  565.  
  566.     8.5 inch 0 inch translate
  567.      90 rotate
  568.  
  569. As just demonstrated, rotate is another very useful PostScript command.  It rotates the user coordinate 
  570. system a specified number of degrees in the counterclockwise direction.  The command 90 rotate would 
  571. change a line that should have been drawn as a horizontal line to one that is drawn vertically.  Rotate 
  572. affects all graphics operators after the command is entered.  The rotate command is also cumulative as 
  573. is the translate command.  Two rotate commands such as
  574.      60 rotate
  575.      60 rotate
  576. would rotate the user coordinate system by a total of 120 degrees.
  577.  
  578. Another very useful PostScript command is the scale command.  This command enables the programmer 
  579. to expand or contract almost any object.  For example, if a column graph's dimensions are 8.5 inches 
  580. wide by 11 inches high, the scale command makes it a simple matter to reduce the graph to one half the 
  581. size.  If a scale command of 0.5 0.5 scale is placed before the commands that draw the graph, the graph 
  582. is drawn as 4.25 by 5.5 inches in size.
  583.  
  584. The scale command works equally well on font size.  Because the scale command can alter the X scale 
  585. and Y scale independently (X Y scale), it is a simple matter to make a normally proportionate font 
  586. appear as a short fat font or a tall thin font.
  587.  
  588. Two very important PostScript commands are gsave and grestore.  These two commands save and 
  589. restore the current graphics state respectively.  A graphics state includes such things as the current path, 
  590. point, setgray value, font, coordinate system, and line thickness.  These two commands allow us to 
  591. change a global setting and only affect one part of a larger drawing.  For example, if a programmer 
  592. wants to scale the width of a font for only one text string right in the middle of a drawing, the user can 
  593. use a gsave, grestore pair.
  594.  
  595.      gsave
  596.     1 inch 1 inch moveto
  597.      3.0 1.0 scale
  598.      (Wide text) show
  599.      grestore
  600.  
  601. Another common use of the gsave, grestore pair is to fill an object and then outline the same object.  As 
  602. learned previously, the creation of a filled box requires defining the path and then calling the fill 
  603. operator.  The fill operator erases the current path.  Therefore, a subsequent stroke operation generates 
  604. an error.  If a gsave command is entered prior to the fill operator, it is possible to save the current path so 
  605. the stroke operator can use it.  A sample code segment is presented.
  606.  
  607.  newpath
  608.  0.0 setgray
  609.  1 inch 1 inch moveto
  610.  1 inch 2 inch lineto
  611.  2 inch 2 inch lineto
  612.  2 inch 1 inch lineto
  613.  closepath
  614.  gsave
  615.      0.5 setgray
  616.      fill
  617.  grestore
  618.  stroke
  619.  showpage
  620.  
  621. The PostScript examples previously discussed created a drawing immediately.  This is not too useful if a 
  622. user wants to save a drawing in a computer readable format that can be imported into another program.  
  623. Fortunately, PostScript allows us to encapsulate these commands in a file that can be loaded into another 
  624. program for inclusion in that program's output.  Adobe has appropriately named this encapsulated set of 
  625. PostScript commands an Encapsulated PostScript File format (EPSF, or EPS for short).
  626.  
  627. The creation of an EPS file is simple once the programmer understands the graphics commands.  An 
  628. EPS file is nothing more than two special lines appended before any graphics commands that defines a 
  629. drawing.  These two lines are as follows.
  630.  
  631.     %!PS-Adobe-3.0 EPSF-3.0
  632.     %%BoundingBox: x1 y1 x2 y2
  633.  
  634. where x1, y1 describe the lower left corner and x2, y2 describe the upper right corner of a bounding box.
  635.  
  636. The bounding box parameters inform a program that loads in the EPS file about the size of the drawing 
  637. contained in the EPS file.  This assists the loading program in displaying and clipping the EPS file 
  638. correctly.  The bounding box must be large enough that every graphic element in the drawing can fit and 
  639. is completely visible inside the box.
  640.  
  641. The following code is a sample EPS file.
  642.  
  643. %!PS-Adobe-3.0 EPSF-3.0
  644. %%BoundingBox: 71 71 145 145
  645. /inch {72 mul} def
  646. 2 setlinewidth
  647. 0.5 setgray
  648. 1 inch 1 inch moveto
  649. 2 inch 2 inch lineto
  650. stroke
  651. showpage
  652.  
  653. Note that the bounding box dimensions are in default PostScript user coordinates.  Because the 
  654. EPS-defining header lines must be the first two lines of the file, any definition such as our /inch 
  655. definition cannot be used to describe the size of the bounding box.  Another important point to 
  656. remember is that graphics elements, such as lines, have thickness.  The bounding box must take the 
  657. thickness of lines into account.  If we defined the bounding box to be 72 72 144 144, we may have cut 
  658. off part of our two unit thick line.
  659.  
  660. The PostScript commands presented compose only a small part of the entire PostScript language.  The 
  661. ways the commands were used and the C functions demonstrated were kept extremely simple for reasons 
  662. of clarity.  There are more efficient methods to perform almost every operation.  For the casual 
  663. programmer, ease of implementation is probably a better use of time than worrying about more involved 
  664. coding methods that shorten printing time.
  665.  
  666. Many published texts exist to guide the serious PostScript programmer.  I recommend first getting the 
  667. PostScript Language Tutorial and Cookbook, Adobe Systems, Inc., Addison-Wesley Publishing.  This 
  668. text introduces the basics of PostScript coding and has many useful examples.  Although not a great text 
  669. for the faint of heart, any serious PostScript programmer should also have the PostScript Language 
  670. Reference Manual by Adobe Systems, Inc., Addison-Wesley, Second Edition.  In addition to the two 
  671. texts created by Adobe Systems, Adobe has a developer support group for the professional PostScript 
  672. programmer (415/961-4111).
  673.  
  674. I have created a very simple program called StructDraw.c that can create and send shapes to an Amiga 
  675. window, a PostScript device, an HP-GL/2 capable plotter, or save the shapes as a DXF wireframe 
  676. drawing.  A description of the HP-GL/2 code appears in the next article.  The code in StructDraw.c was 
  677. kept as simple as possible for clarity sake.  To keep the code short, the program does not support any 
  678. user interaction.  The user will have to alter and recompile the code if they want to send the output to a 
  679. different location or change the shapes drawn. StructDraw.c currently sends Amiga output to an Amiga 
  680. window.  The three other supported formats are saved as files in RAM using the following names: 
  681. tmp.ps, tmp.hpgl, and tmp.dxf.  After the program creates the four drawings it exits.  The interested 
  682. programmer is free to use the graphics functions in any code they choose as long as the code is not 
  683. included verbatim in a commercial product.
  684.  
  685.